package tool

import (
	"reflect"
	"strings"
)

// SliceToMapOkAny slice to map (Custom type, filter)
func SliceToMapOkAny[E any, K comparable, V any](es []E, fn func(e E) (K, V, bool)) map[K]V {
	kv := make(map[K]V)
	for i := 0; i < len(es); i++ {
		t := es[i]
		if k, v, ok := fn(t); ok {
			kv[k] = v
		}
	}
	return kv
}

// SliceToMapAny slice to map (Custom type)
func SliceToMapAny[E any, K comparable, V any](es []E, fn func(e E) (K, V)) map[K]V {
	return SliceToMapOkAny(es, func(e E) (K, V, bool) {
		k, v := fn(e)
		return k, v, true
	})
}

// SliceToMap slice to map
func SliceToMap[E any, K comparable](es []E, fn func(e E) K) map[K]E {
	return SliceToMapOkAny(es, func(e E) (K, E, bool) {
		k := fn(e)
		return k, e, true
	})
}

// SliceSetAny slice to map[K]struct{}
func SliceSetAny[E any, K comparable](es []E, fn func(e E) K) map[K]struct{} {
	return SliceToMapAny(es, func(e E) (K, struct{}) {
		return fn(e), struct{}{}
	})
}

func Filter[E, T any](es []E, fn func(e E) (T, bool)) []T {
	rs := make([]T, 0, len(es))
	for i := 0; i < len(es); i++ {
		e := es[i]
		if t, ok := fn(e); ok {
			rs = append(rs, t)
		}
	}
	return rs
}

// Slice Converts slice types in batches
func Slice[E any, T any](es []E, fn func(e E) T) []T {
	v := make([]T, len(es))
	for i := 0; i < len(es); i++ {
		v[i] = fn(es[i])
	}
	return v
}

// SliceSet slice to map[E]struct{}
func SliceSet[E comparable](es []E) map[E]struct{} {
	return SliceSetAny(es, func(e E) E {
		return e
	})
}

// HasKey get whether the map contains key
func HasKey[K comparable, V any](m map[K]V, k K) bool {
	if m == nil {
		return false
	}
	_, ok := m[k]
	return ok
}

// FindUnknownKeys returns a list of keys from map not existing in struct JSON tags.
func FindUnknownKeys[T any](jsonMap map[string]any, data T) []string {
	jsonKeys := make(map[string]bool)
	for key := range jsonMap {
		jsonKeys[key] = true
	}

	structKeys := getStructJSONTags(reflect.TypeOf(data))

	unknownKeys := make([]string, 0)
	for key := range jsonKeys {
		if !structKeys[key] {
			unknownKeys = append(unknownKeys, key)
		}
	}

	return unknownKeys
}

// getStructJSONTags returns a map of JSON tags found in the struct (including embedded structs).
func getStructJSONTags(t reflect.Type) map[string]bool {
	structKeys := make(map[string]bool)
	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)

		// Check if the field is an embedded struct and recursively get its fields.
		if field.Anonymous && field.Type.Kind() == reflect.Struct {
			embeddedFields := getStructJSONTags(field.Type)
			for key := range embeddedFields {
				structKeys[key] = true
			}
			continue
		}

		// Extract the JSON tag.
		jsonKey := strings.TrimSuffix(field.Tag.Get("json"), ",omitempty")
		if jsonKey != "" {
			structKeys[jsonKey] = true
		}
	}

	return structKeys
}
